package com.flow.framework.api.doc.plugins;

import com.fasterxml.classmate.ResolvedType;
import com.flow.framework.api.doc.util.ImmutableUtil;
import com.flow.framework.common.constant.FrameworkCommonConstant;
import com.flow.framework.common.util.verify.VerifyUtil;
import com.flow.framework.core.validation.annotation.ImmutableCustomization;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.schema.Collections;
import springfox.documentation.schema.Enums;
import springfox.documentation.schema.Example;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.AllowableValues;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.EnumTypeDeterminer;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;
import springfox.documentation.spring.web.DescriptionResolver;
import springfox.documentation.swagger.schema.ApiModelProperties;

import java.lang.reflect.Field;

import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static springfox.documentation.swagger.common.SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER;
import static springfox.documentation.swagger.common.SwaggerPluginSupport.pluginDoesApply;
import static springfox.documentation.swagger.readers.parameter.Examples.examples;

/**
 * 参数解析器 参考springfox.documentation.swagger.readers.parameter.ApiParamParameterBuilder
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2023/3/19
 */
@RequiredArgsConstructor
public class CustomizationApiParamParameterBuilder implements ParameterBuilderPlugin {
    private final DescriptionResolver descriptions;
    private final EnumTypeDeterminer enumTypeDeterminer;

    @Override
    @SuppressWarnings("deprecation")
    public void apply(ParameterContext context) {
        ParameterBuilder parameterBuilder = context.parameterBuilder();
        Optional<ApiParam> apiParam = context.resolvedMethodParameter().findAnnotation(ApiParam.class);
        parameterBuilder
                .allowableValues(allowableValues(
                        context.alternateFor(context.resolvedMethodParameter().getParameterType()),
                        apiParam.transform(toAllowableValue()).or(FrameworkCommonConstant.EMPTY_STRING)));
        String description = FrameworkCommonConstant.EMPTY_STRING;
        String example = FrameworkCommonConstant.EMPTY_STRING;
        if (apiParam.isPresent()) {
            ApiParam annotation = apiParam.get();
            description = annotation.value();
            parameterBuilder.name(emptyToNull(annotation.name()))
                    .parameterAccess(emptyToNull(annotation.access()))
                    .defaultValue(emptyToNull(annotation.defaultValue()))
                    .allowMultiple(annotation.allowMultiple())
                    .allowEmptyValue(annotation.allowEmptyValue())
                    .required(annotation.required())
                    .complexExamples(examples(annotation.examples()))
                    .hidden(annotation.hidden())
                    .collectionFormat(annotation.collectionFormat())
                    .order(SWAGGER_PLUGIN_ORDER);
            example = annotation.example();
        }

        Class<?> clazz = context.resolvedMethodParameter().getParameterType().getErasedType();
        String additionalDescription = ImmutableUtil.getAdditionalDescription(clazz);
        if (!VerifyUtil.isEmpty(additionalDescription)) {
            example = ImmutableUtil.getExample(clazz);
        } else {
            Optional<ImmutableCustomization> immutableCustomizationOptional = context.resolvedMethodParameter().findAnnotation(ImmutableCustomization.class);
            additionalDescription = ImmutableUtil.getAdditionalDescription(immutableCustomizationOptional);
            if (!VerifyUtil.isEmpty(additionalDescription)) {
                example = ImmutableUtil.getExample(immutableCustomizationOptional);
            }
        }

        if (!VerifyUtil.isEmpty(additionalDescription)) {
            try {
                Field allowableValues = parameterBuilder.getClass().getField("allowableValues");
                allowableValues.setAccessible(true);
                allowableValues.set(parameterBuilder, null);
                allowableValues.setAccessible(false);

                ModelRef modelRef = new ModelRef("string", null, null, false);
                Field refAllowableValues = ParameterBuilder.class.getDeclaredField("modelRef");
                refAllowableValues.setAccessible(true);
                refAllowableValues.set(this, modelRef);
                refAllowableValues.setAccessible(false);
            } catch (Exception ignore) {
            }
        }
        String finalDescription = description + additionalDescription;
        descriptions.resolve(finalDescription);
        parameterBuilder.description(finalDescription);
        parameterBuilder.scalarExample(new Example(example));
    }

    private Function<ApiParam, String> toAllowableValue() {
        return new Function<ApiParam, String>() {
            @Override
            public String apply(ApiParam input) {
                return input.allowableValues();
            }
        };
    }

    private AllowableValues allowableValues(ResolvedType parameterType, String allowableValueString) {
        AllowableValues allowableValues = null;
        if (!isNullOrEmpty(allowableValueString)) {
            allowableValues = ApiModelProperties.allowableValueFromString(allowableValueString);
        } else {
            if (enumTypeDeterminer.isEnum(parameterType.getErasedType())) {
                allowableValues = Enums.allowableValues(parameterType.getErasedType());
            }
            if (Collections.isContainerType(parameterType)) {
                allowableValues = Enums.allowableValues(Collections.collectionElementType(parameterType).getErasedType());
            }
        }
        return allowableValues;
    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return pluginDoesApply(delimiter);
    }
}
